﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Foundation.ExtensionMethods;
using Brushes = System.Windows.Media.Brushes;
using Pen = System.Windows.Media.Pen;
using Point = System.Windows.Point;

namespace Foundation
{
    public static class GridViewExtensions
    {
        public static readonly DependencyPropertyKey InvisibleColumnsPropertyKey =
            DependencyProperty.RegisterAttachedReadOnly("InvisibleColumns", typeof (GridViewColumnCollection),
                                                        typeof (GridViewExtensions),
                                                        new PropertyMetadata(new GridViewColumnCollection()));

        public static GridViewColumnCollection GetInvisibleColumns(this GridView gridView)
        {
            return (GridViewColumnCollection) gridView.GetValue(InvisibleColumnsPropertyKey.DependencyProperty);
        }

        public static readonly DependencyProperty OwnerProperty =
            DependencyProperty.RegisterAttached("Owner", typeof (ListView), typeof (GridViewExtensions),
                                                new PropertyMetadata(default(ListView)));

        public static ListView GetOwner(this GridViewColumn obj)
        {
            return (ListView) obj.GetValue(OwnerProperty);
        }

        public static void SetOwner(this GridViewColumn obj, ListView value)
        {
            obj.SetValue(OwnerProperty, value);
        }

        public static readonly DependencyProperty IsVisibleProperty =
            DependencyProperty.RegisterAttached(
                "IsVisible", typeof (bool), typeof (GridViewExtensions),
                new PropertyMetadata(true, (o, args) =>
                    {
                        var column = o as GridViewColumn;
                        if (column == null) return ;
                        var listView = column.GetOwner();
                        if (listView == null) return;
                        var gridView = listView.View as GridView;
                        if (gridView == null) return;
                        var invisibleColumns = gridView.GetInvisibleColumns();

                        if (Equals(args.NewValue, true) && !gridView.Columns.Contains(column))
                        {
                            if (invisibleColumns.Contains(column))
                                invisibleColumns.Remove(column);
                            var index = column.GetIndex();
                            if (index < 0 || index >= gridView.Columns.Count)
                                gridView.Columns.Add(column);
                            else gridView.Columns.Insert(index, column);
                        }

                        if (Equals(args.NewValue, false) && gridView.Columns.Contains(column))
                        {
                            gridView.Columns.Remove(column);
                            invisibleColumns.Add(column);
                            
                        }
                    }));

        public static bool GetIsVisible(this GridViewColumn obj)
        {
            return (bool) obj.GetValue(IsVisibleProperty);
        }

        public static void SetIsVisible(this GridViewColumn obj, bool value)
        {
            obj.SetValue(IsVisibleProperty, value);
        }

        public static readonly DependencyProperty MulticolumnSortingProperty =
            DependencyProperty.RegisterAttached("MulticolumnSorting", typeof (bool), typeof (GridViewExtensions),
                                                new PropertyMetadata(default(bool)));

        public static bool GetMulticolumnSorting(this ListView obj)
        {
            return (bool) obj.GetValue(MulticolumnSortingProperty);
        }

        public static void SetMulticolumnSorting(this ListView obj, bool value)
        {
            obj.SetValue(MulticolumnSortingProperty, value);
        }

        public static readonly DependencyProperty SortDescriptionCollectionProperty =
            DependencyProperty.RegisterAttached(
                "SortDescriptionCollection",
                typeof (object),
                typeof (GridViewExtensions),
                new FrameworkPropertyMetadata(
                    null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    (o, e) =>
                        {
                            var listView = o as ListView;
                            if (listView == null) return;

                            listView.Loaded += (sender, args) =>
                                {
                                    if (e.OldValue == e.NewValue || e.NewValue == null) return;
                                    var loadedSortDescriptions =
                                        ((IEnumerable) e.NewValue).OfType<KeyValuePair<string, string>>().ToList();
                                    listView.Items.SortDescriptions.Clear();
                                    listView.Items.SortDescriptions.AddRange(
                                        loadedSortDescriptions.Select(
                                            i =>
                                                {
                                                    var direction =
                                                        (ListSortDirection)
                                                        Enum.Parse(typeof (ListSortDirection), i.Value);
                                                    return new SortDescription(i.Key, direction);
                                                }));

                                    var list = GetSortDescriptionCollection(listView) ??
                                               new Dictionary<string, string>();
                                    //listView.Items.SortDescriptions.ForEach(d => list.Add(d.PropertyName, d.Direction.ToString()));
                                    SetSortDescriptionCollection(listView, list);
                                    var sortDescriptions = listView.Items.SortDescriptions;
                                    var gridView = listView.View as GridView;
                                    if (gridView == null) return;
                                    foreach (var column in gridView.Columns)
                                    {
                                        var propertyName = GetPropertyName(column);
                                        if (propertyName == null ||
                                            sortDescriptions.All(d => d.PropertyName != propertyName)) continue;
                                        var sortDescription = sortDescriptions.First(d => d.PropertyName == propertyName);
                                        var direction = sortDescription.Direction;
                                        if (GetShowSortGlyph(listView))
                                            AddSortGlyph(
                                                (GridViewColumnHeader) column.Header,
                                                direction,
                                                direction == ListSortDirection.Ascending
                                                    ? GetSortGlyphAscending(listView)
                                                    : GetSortGlyphDescending(listView));
                                        SetSortedColumnHeader(listView, (GridViewColumnHeader) column.Header);
                                    }
                                };
                        }));

        public static object GetSortDescriptionCollection(DependencyObject obj)
        {
            return obj.GetValue(SortDescriptionCollectionProperty);
        }

        public static void SetSortDescriptionCollection(DependencyObject obj, object value)
        {
            obj.SetValue(SortDescriptionCollectionProperty, value);
        }

        #region Public attached properties

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand) obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command",
                typeof (ICommand),
                typeof (GridViewExtensions),
                new UIPropertyMetadata(
                    null,
                    (o, e) =>
                        {
                            var listView = o as ItemsControl;
                            if (listView == null) return;
                            if (GetAutoSort(listView)) return;
                            if (e.OldValue != null && e.NewValue == null)
                            {
                                listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                            }
                            if (e.OldValue == null && e.NewValue != null)
                            {
                                listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                            }
                        }
                    ));

        public static bool GetAutoSort(DependencyObject obj)
        {
            return (bool) obj.GetValue(AutoSortProperty);
        }

        public static void SetAutoSort(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoSortProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoSortProperty =
            DependencyProperty.RegisterAttached(
                "AutoSort",
                typeof (bool),
                typeof (GridViewExtensions),
                new UIPropertyMetadata(
                    false,
                    (o, e) =>
                        {
                            var listView = o as ListView;
                            if (listView == null) return;

                            if (GetCommand(listView) != null) return;
                            var oldValue = (bool) e.OldValue;
                            var newValue = (bool) e.NewValue;
                            if (oldValue && !newValue)
                            {
                                listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                SetSortDescriptionCollection(listView, null);
                            }
                            if (!oldValue && newValue)
                            {
                                listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
                                SetSortDescriptionCollection(listView, new Dictionary<string, string>());
                            }
                        }
                    ));

        public static string GetPropertyName(DependencyObject obj)
        {
            return (string) obj.GetValue(PropertyNameProperty);
        }

        public static void SetPropertyName(DependencyObject obj, string value)
        {
            obj.SetValue(PropertyNameProperty, value);
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.RegisterAttached(
                "PropertyName",
                typeof (string),
                typeof (GridViewExtensions),
                new UIPropertyMetadata(null));

        public static bool GetShowSortGlyph(DependencyObject obj)
        {
            return (bool) obj.GetValue(ShowSortGlyphProperty);
        }

        public static void SetShowSortGlyph(DependencyObject obj, bool value)
        {
            obj.SetValue(ShowSortGlyphProperty, value);
        }

        // Using a DependencyProperty as the backing store for ShowSortGlyph.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowSortGlyphProperty =
            DependencyProperty.RegisterAttached("ShowSortGlyph", typeof (bool), typeof (GridViewExtensions),
                                                new UIPropertyMetadata(true));

        public static ImageSource GetSortGlyphAscending(DependencyObject obj)
        {
            return (ImageSource) obj.GetValue(SortGlyphAscendingProperty);
        }

        public static void SetSortGlyphAscending(DependencyObject obj, ImageSource value)
        {
            obj.SetValue(SortGlyphAscendingProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortGlyphAscending.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SortGlyphAscendingProperty =
            DependencyProperty.RegisterAttached("SortGlyphAscending", typeof (ImageSource), typeof (GridViewExtensions),
                                                new UIPropertyMetadata(null));

        public static ImageSource GetSortGlyphDescending(DependencyObject obj)
        {
            return (ImageSource) obj.GetValue(SortGlyphDescendingProperty);
        }

        public static void SetSortGlyphDescending(DependencyObject obj, ImageSource value)
        {
            obj.SetValue(SortGlyphDescendingProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortGlyphDescending.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SortGlyphDescendingProperty =
            DependencyProperty.RegisterAttached("SortGlyphDescending", typeof (ImageSource), typeof (GridViewExtensions),
                                                new UIPropertyMetadata(null));

        #endregion

        #region Private attached properties

        private static void SetSortedColumnHeader(this DependencyObject obj, GridViewColumnHeader value)
        {
            obj.SetValue(SortedColumnHeaderProperty, value);
        }

        // Using a DependencyProperty as the backing store for SortedColumn.  This enables animation, styling, binding, etc...
        private static readonly DependencyProperty SortedColumnHeaderProperty =
            DependencyProperty.RegisterAttached("SortedColumnHeader", typeof (GridViewColumnHeader),
                                                typeof (GridViewExtensions), new UIPropertyMetadata(null));

        #endregion

        #region Column header click event handler

        private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
        {
            var headerClicked = e.OriginalSource as GridViewColumnHeader;
            if (headerClicked == null || headerClicked.Column == null) return;
            var propertyName = GetPropertyName(headerClicked.Column);
            if (string.IsNullOrEmpty(propertyName)) return;
            var listView = headerClicked.GetNearestVisualAncestor<ListView>();
            if (listView == null) return;
            var command = GetCommand(listView);
            if (command != null)
            {
                if (command.CanExecute(propertyName))
                    command.Execute(propertyName);
            }
            else if (GetAutoSort(listView))
            {
                ApplySort(propertyName, listView, headerClicked);
            }
        }

        #endregion

        #region Helper methods

        private static void ApplySort(string propertyName, ListView listView, GridViewColumnHeader sortedColumnHeader)
        {
            var view = listView.Items;
            var gridView = (GridView) listView.View;
            var direction = ListSortDirection.Ascending;
            if (view.SortDescriptions.Any(d => d.PropertyName == propertyName))
            {
                foreach (var column in gridView.Columns)
                {
                    var header = column.Header as GridViewColumnHeader;
                    if (header == null) continue;

                    var propertyName1 = GetPropertyName(column);
                    if (propertyName == propertyName1) RemoveSortGlyph(header);
                }

                var currentSort = view.SortDescriptions.First(d => d.PropertyName == propertyName);
                view.SortDescriptions.Remove(currentSort);
                if (currentSort.Direction == ListSortDirection.Descending)
                {
                    if (!GetMulticolumnSorting(listView)) view.SortDescriptions.Clear();
                    var dic1 = listView.Items.SortDescriptions.ToDictionary(
                        d => d.PropertyName, d => d.Direction.ToString());
                    SetSortDescriptionCollection(listView, dic1);
                    return;
                }

                //var currentSortedColumnHeader = GetSortedColumnHeader(listView);
                // if (currentSortedColumnHeader != null)
                // RemoveSortGlyph(currentSortedColumnHeader);

                direction = ListSortDirection.Descending;
            }

            if (!GetMulticolumnSorting(listView)) view.SortDescriptions.Clear();
            view.SortDescriptions.Insert(0, new SortDescription(propertyName, direction));

            var dic = listView.Items.SortDescriptions.ToDictionary(
                d => d.PropertyName, d => d.Direction.ToString());
            SetSortDescriptionCollection(listView, dic);


            if (GetShowSortGlyph(listView))
            {
                foreach (var column in gridView.Columns)
                {
                    var header = column.Header as GridViewColumnHeader;
                    if (header == null) continue;
                    RemoveSortGlyph(header);
                    var propertyName1 = GetPropertyName(column);
                    if (view.SortDescriptions.Any(d => d.PropertyName == propertyName1))
                    {
                        direction = view.SortDescriptions.First(d => d.PropertyName == propertyName1).Direction;
                        AddSortGlyph(
                            header,
                            direction,
                            direction == ListSortDirection.Ascending
                                ? GetSortGlyphAscending(listView)
                                : GetSortGlyphDescending(listView));
                    }
                }
            }

            SetSortedColumnHeader(listView, sortedColumnHeader);
        }

        private static void AddSortGlyph(GridViewColumnHeader columnHeader, ListSortDirection direction,
                                         ImageSource sortGlyph)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
            adornerLayer.Add(new SortGlyphAdorner(columnHeader, direction, sortGlyph));
        }

        private static void RemoveSortGlyph(UIElement columnHeader)
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);
            var adorners = adornerLayer.GetAdorners(columnHeader);
            if (adorners == null) return;
            adorners.OfType<SortGlyphAdorner>().ForEach(adornerLayer.Remove);
        }

        #endregion

        #region SortGlyphAdorner nested class

        private class SortGlyphAdorner : Adorner
        {
            private readonly GridViewColumnHeader _columnHeader;
            private readonly ListSortDirection _direction;
            private readonly ImageSource _sortGlyph;

            public SortGlyphAdorner(GridViewColumnHeader columnHeader, ListSortDirection direction,
                                    ImageSource sortGlyph)
                : base(columnHeader)
            {
                _columnHeader = columnHeader;
                _direction = direction;
                _sortGlyph = sortGlyph;
            }

            private Geometry GetDefaultGlyph()
            {
                var x1 = _columnHeader.ActualWidth - 13;
                var x2 = x1 + 10;
                var x3 = x1 + 5;
                var y1 = _columnHeader.ActualHeight/2 - 3;
                var y2 = y1 + 5;

                if (_direction == ListSortDirection.Ascending)
                {
                    var tmp = y1;
                    y1 = y2;
                    y2 = tmp;
                }

                var pathSegmentCollection = new PathSegmentCollection
                    {
                        new LineSegment(new Point(x2, y1), true),
                        new LineSegment(new Point(x3, y2), true)
                    };

                var pathFigure = new PathFigure(new Point(x1, y1), pathSegmentCollection, true);
                var pathFigureCollection = new PathFigureCollection {pathFigure};
                var pathGeometry = new PathGeometry(pathFigureCollection);
                return pathGeometry;
            }

            protected override void OnRender(DrawingContext drawingContext)
            {
                base.OnRender(drawingContext);

                if (_sortGlyph != null)
                {
                    var x = _columnHeader.ActualWidth - 13;
                    var y = _columnHeader.ActualHeight/2 - 5;
                    var rect = new Rect(x, y, 10, 10);
                    drawingContext.DrawImage(_sortGlyph, rect);
                }
                else drawingContext.DrawGeometry(Brushes.Silver, new Pen(Brushes.Silver, 0), GetDefaultGlyph());
            }
        }

        #endregion

        #region SaveColumnOrder

        public static bool GetSaveColumnOrder(this ListView obj)
        {
            return (bool) obj.GetValue(SaveColumnOrderProperty);
        }

        public static void SetSaveColumnOrder(this ListView obj, bool value)
        {
            obj.SetValue(SaveColumnOrderProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SaveColumnOrderProperty =
            DependencyProperty.RegisterAttached(
                "SaveColumnOrderReorder",
                typeof (bool),
                typeof (GridViewExtensions),
                new UIPropertyMetadata(
                    false,
                    (o, e) =>
                        {
                            var listView = o as ListView;
                            if (listView == null) return;
                            var oldValue = (bool) e.OldValue;
                            var newValue = (bool) e.NewValue;

                            if (oldValue && !newValue)
                            {
                                var gridView = listView.View as GridView;
                                if (gridView == null) return;
                                gridView.Columns.CollectionChanged -= ColumnsOnCollectionChanged;
                            }

                            if (!oldValue && newValue)
                            {
                                listView.LayoutUpdated += (sender1, eventArgs) =>
                                    {
                                        var gridView = listView.View as GridView;
                                        if (gridView == null) return;
                                        foreach (var column in gridView.Columns.ToList())
                                        {
                                            var minWidth = column.GetMinWidth();
                                            var maxWidth = column.GetMaxWidth();
                                            if (maxWidth >= 0 && column.Width > maxWidth)
                                                column.Width = maxWidth;
                                            if (minWidth >= 0 && column.Width < minWidth)
                                                column.Width = minWidth;
                                        }
                                    };

                                listView.Loaded += (sender, args) =>
                                    {
                                        var gridView = listView.View as GridView;
                                        if (gridView == null) return;

                                        var columns = gridView.Columns.ToList();
                                        columns.ForEach(c => c.SetOwner(listView));
                                        columns.Sort((x, y) => GetIndex(x) - GetIndex(y));
                                        gridView.Columns.CollectionChanged -= ColumnsOnCollectionChanged;
                                        gridView.Columns.Clear();
                                        var invisibleColumns = gridView.GetInvisibleColumns();
                                        columns.Where(c => !c.GetIsVisible()).ForEach(invisibleColumns.Add);
                                        columns.RemoveAll(c => !c.GetIsVisible());
                                        columns.ForEach(c => c.SetIndex(columns.IndexOf(c)));
                                        columns.ForEach(c => gridView.Columns.Add(c));
                                        gridView.Columns.CollectionChanged += ColumnsOnCollectionChanged;
                                    };
                            }
                        }
                    ));

        private static void ColumnsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            var collumns = (GridViewColumnCollection) sender;
            for (var i = 0; i < collumns.Count; i++)
            {
                SetIndex(collumns[i], i);
            }
        }

        public static int GetIndex(this GridViewColumn obj)
        {
            return (int) obj.GetValue(IndexProperty);
        }

        public static void SetIndex(this GridViewColumn obj, int value)
        {
            obj.SetValue(IndexProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.RegisterAttached(
                "Index",
                typeof (int),
                typeof (GridViewExtensions),
                new FrameworkPropertyMetadata(-1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        public static readonly DependencyProperty MinWidthProperty =
            DependencyProperty.RegisterAttached("MinWidth", typeof (double), typeof (GridViewExtensions),
                                                new PropertyMetadata(-1.0));

        public static double GetMinWidth(this GridViewColumn obj)
        {
            return (double) obj.GetValue(MinWidthProperty);
        }

        public static void SetMinWidth(this GridViewColumn obj, double value)
        {
            obj.SetValue(MinWidthProperty, value);
        }

        public static readonly DependencyProperty MaxWidthProperty =
            DependencyProperty.RegisterAttached("MaxWidth", typeof (double), typeof (GridViewExtensions),
                                                new PropertyMetadata(-1.0));

        public static double GetMaxWidth(this GridViewColumn obj)
        {
            return (double)obj.GetValue(MaxWidthProperty);
        }

        public static void SetMaxWidth(this GridViewColumn obj, double value)
        {
            obj.SetValue(MaxWidthProperty, value);
        }

        #endregion
    }
}
